#!/usr/bin/env python
# -*- coding: utf-8; py-indent-offset:4 -*-
###############################################################################
#
# Copyright (C) 2015-2020 Daniel Rodriguez
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)


import collections
import os
import backtrader as bt
from backtrader import Order, Position
from backtrader.utils.datehelper import *
import pandas as pd

"""
成交历史
"""


class Orders(bt.Analyzer):
    '''This analyzer reports the trades history occurred with each an every data in
    the system

    It looks at the order execution bits to create a ``Position`` starting from
    0 during each ``next`` cycle.

    The result is used during next to record the transactions

    Params:

      - headers (default: ``True``)

        Add an initial key to the dictionary holding the results with the names
        of the datas

        This analyzer was modeled to facilitate the integration with
        ``pyfolio`` and the header names are taken from the samples used for
        it::

          'date', 'amount', 'price', 'sid', 'symbol', 'value'
          - amount: 持仓大小
          - price: 持仓平均价格
          - value: 持仓权益

    Methods:

      - get_analysis

        Returns a dictionary with returns as values and the datetime points for
        each return as keys
    '''
    params = (
        ('headers', False),
        ('_pfheaders', ('date', 'symbol', 'createTime', 'size', 'price', 'opened', 'closed', 'openedvalue',
                        'closedvalue', 'openedcomm', 'closedcomm', 'value', 'comm', 'pnl', 'possize', 'posprice', 'tag')),
        ('csv', False),
        ('out', None),
        ('rounding', 6)
    )

    def start(self):
        super(Orders, self).start()
        if self.p.headers:
            self.rets[self.p._pfheaders[0]] = [list(self.p._pfheaders[1:])]

        # self._positions = collections.defaultdict(Position)
        # self._idnames = list(enumerate(self.strategy.getdatanames()))

    def notify_order(self, order: bt.Order):
        # An order could have several partial executions per cycle (unlikely
        # but possible) and therefore: collect each new execution notification
        # and let the work for next

        # We use a fresh Position object for each round to get summary of what
        # the execution bits have done in that round
        if order.status not in [Order.Partial, Order.Completed]:
            return  # It's not an execution

        symbol = order.data._name
        for exbit in order.executed.iterpending():
            if exbit is None:
                break  # end of pending reached
            item = [
                symbol, date2str(bt.utils.num2date(order.created.dt)), exbit.size, exbit.price, exbit.opened, exbit.closed, exbit.openedvalue, exbit.closedvalue,
                exbit.openedcomm, exbit.closedcomm, exbit.value, exbit.comm, exbit.pnl, exbit.psize, exbit.pprice, exbit.tag
            ]
            if bt.utils.num2date(exbit.dt) not in self.rets:
                self.rets[bt.utils.num2date(exbit.dt)] = [item]
            else:
                self.rets[bt.utils.num2date(exbit.dt)].append(item)

    def stop(self):
        if self.p.csv and self.p.out:
            params = self.get_params()
            report_dir = "_".join(map(lambda x: str(x), params.values()))
            out = os.path.join(self.p.out, report_dir)

            if not os.path.exists(out):
                os.makedirs(out)

            fpath = os.path.join(out, "orders.csv")
            self.to_dataframe().to_csv(fpath, index=False)

    def to_dataframe(self):
        cols = self.p._pfheaders
        data = []
        _skip_headers = self.p.headers
        for date, v in self.rets.items():
            if _skip_headers:
                _skip_headers = False
                continue
            if isinstance(v, list):
                for v1 in v:
                    data.append([date2str(date)] + v1)

        df = pd.DataFrame(columns=cols, data=data)
        df = df.round(decimals=self.p.rounding)
        if len(df) > 0:
            for col in cols:
                if col not in ['size', 'price', 'opened', 'closed', 'openedvalue',
                            'closedvalue', 'openedcomm', 'closedcomm', 'value', 'comm', 'pnl', 'possize', 'posprice']:
                    continue
                pad = df[col].astype(str).str.len().max()
                df[col] = df[col].astype(str).str.ljust(pad, "0")
        return df


